-- lantern.lua

assert(type(lantern) == "table")

-- NOTE: Lantern overfill up to 124% is a feature, not a bug. ;-)
--       but be carefull overfilling the lantern, may cause a fire.

-- ==== SETTINGS ====
-- fuel material
lantern.fuel = "basic_materials:oil_extract"

-- fuel max in bottles
lantern.fuel_max = 4
-- fuel time in seconds (per fuel bottle)
lantern.fuel_time = 15*60
-- fuel multiplier on dim light (x times longer burn)
-- low fuel is fuel_time/fuel_dim, on this level lamp only turns dim
lantern.fuel_dim = 4
-- fuel multiplier on med light (x times longer burn)
lantern.fuel_med = 2

-- mode light   duration
-- ==== ==-==== =====
-- full 13 100%  1.00
-- med   9  69%  1.50
-- dim   6  46%  2.20
-- off   1   7% 14.30 N/A

lantern.bright_full = 13
lantern.bright_med = 9
lantern.bright_dim = 6
lantern.bright_off = 1

-- == GENERIC ==
lantern.nodelist = {
	"lantern:lantern_floor_on",
	"lantern:lantern_floor_med",
	"lantern:lantern_floor_dim",
	"lantern:lantern_floor_off",
	"lantern:lantern_ceiling_on",
	"lantern:lantern_ceiling_med",
	"lantern:lantern_ceiling_dim",
	"lantern:lantern_ceiling_off",
	"lantern:lantern_wall_on",
	"lantern:lantern_wall_med",
	"lantern:lantern_wall_dim",
	"lantern:lantern_wall_off"
}

local function listcontains(list, obj)
	for i, v in ipairs(list) do
		if obj == v then
			return true
		end
	end
	return false
end

function lantern.turnoff(pos)
	-- stop dim timer
	-- stop on timer
	-- update burntime
	local sside = lantern.get_side(pos)
	local sname = string.format("lantern:lantern_%s_", sside)
	local node = minetest.get_node(pos)
	local timer = minetest.get_node_timer(pos)

	--if not timer:is_started() then
	--	return false
	--end

	local meta = minetest.get_meta(pos)
	local burntime = meta:get_int("burntime") or 0
	local timeout = timer:get_timeout()
	local elapsed = timer:get_elapsed()
	timer:stop()

	if node.name == sname .. "on" then
		-- TURN OFF
		if elapsed < burntime then
			burntime = burntime - elapsed
		else
			burntime = 0
		end
	elseif node.name == sname .. "med" then
		-- TURN OFF (med)
		burntime = burntime * lantern.fuel_med
		if elapsed < burntime then
			burntime = burntime - elapsed
			burntime = math.floor(burntime / lantern.fuel_med)
		else
			burntime = 0
		end
	elseif node.name == sname .. "dim" then
		-- TURN OFF (dim)
		burntime = burntime * lantern.fuel_dim
		if elapsed < burntime then
			burntime = burntime - elapsed
			burntime = math.floor(burntime / lantern.fuel_dim)
		else
			burntime = 0
		end
	end

	node.name = sname .. "off"
	minetest.swap_node(pos, node)
	meta:set_int("burntime", burntime)
	-- meta:set_string("infotext", string.format("Lantern [%d]", burntime))
	lantern.update_info(pos)
	--return true
end

function lantern.turndim(pos)
	-- timer must be stopped
	-- start dim timer
	local timer = minetest.get_node_timer(pos)
	if timer:is_started() then
		return false
	end
	
	local fuel_min = math.floor(lantern.fuel_time / lantern.fuel_dim)
	local meta = minetest.get_meta(pos)
	local burntime = meta:get_int("burntime") or 0
	if burntime <= 0 then
		-- no fuel
		return false
	end

	-- burntime multiplier on low consumption (dim)
	burntime = burntime * lantern.fuel_dim
	timer:start(burntime)
	
	local node = minetest.get_node(pos)
	local sside = lantern.get_side(pos)
	local sname = string.format("lantern:lantern_%s_dim", sside)
	node.name = sname
	minetest.swap_node(pos, node)
	return true
end

function lantern.turnmed(pos)
	-- timer must be stopped
	-- start med timer
	local timer = minetest.get_node_timer(pos)
	if timer:is_started() then
		return false
	end
	
	local fuel_min = math.floor(lantern.fuel_time / lantern.fuel_dim)
	local meta = minetest.get_meta(pos)
	local burntime = meta:get_int("burntime") or 0
	if burntime <= 0 then
		-- no fuel
		return false
	end

	-- burntime multiplier on med consumption (med)
	burntime = burntime * lantern.fuel_med
	timer:start(burntime)
	
	local node = minetest.get_node(pos)
	local sside = lantern.get_side(pos)
	local sname = string.format("lantern:lantern_%s_med", sside)
	node.name = sname
	minetest.swap_node(pos, node)
	return true
end

function lantern.turnon(pos)
	-- timer must be stopped
	-- start on timer
	local timer = minetest.get_node_timer(pos)
	if timer:is_started() then
		return false
	end

	local fuel_min = math.floor(lantern.fuel_time / lantern.fuel_dim)
	local meta = minetest.get_meta(pos)
	local burntime = meta:get_int("burntime") or 0
	
	if burntime < fuel_min then
		-- low fuel, turn dim instead
		return false
	end

	-- at low fuel only turn dim
	timer:start(burntime - fuel_min)
	
	local sside = lantern.get_side(pos)
	local sname = string.format("lantern:lantern_%s_on", sside)
	local node = minetest.get_node(pos)
	node.name = sname
	minetest.swap_node(pos, node)
	return true
end

function lantern.get_side(pos)
	local node = minetest.get_node(pos)
	if not node then
		return nil
	end

	local sside
	if node.param2 == 0 then
		-- ceiling
		sside = "ceiling"
	elseif node.param2 == 1 then
		-- floor
		sside = "floor"
	else
		-- wall
		sside = "wall"
	end
	return sside
end

-- switch lamp on/dim/off
function lantern.switch(pos)
	local node = minetest.get_node(pos)
	if not node then
		return false
	end

	local sside = lantern.get_side(pos)
	local sname = string.format("lantern:lantern_%s_", sside)

	local timer = minetest.get_node_timer(pos)
	local meta = minetest.get_meta(pos)

	if node.name == sname .. "off" then
		-- TURN DIM
		-- lantern.chat_debug("boxface", "try turning dim")
		if lantern.turndim(pos) then
			--lantern.chat_debug("boxface", "success")
		else
			--lantern.chat_debug("boxface", "failed")
		end
	elseif node.name == sname .. "dim" then
		lantern.turnoff(pos)

		-- TURN MED
		if not lantern.turnmed(pos) then
			lantern.turnoff(pos)
		end
	elseif node.name == sname .. "med" then
		lantern.turnoff(pos)

		-- TURN ON
		--lantern.chat_debug("boxface", "try turning on full")
		-- if low fuel turn off
		if not lantern.turnon(pos) then
			--lantern.chat_debug("boxface", "failed! wtf?")
			lantern.turnoff(pos)
		end
	elseif node.name == sname .. "on" then
		-- TURN OFF
		-- lantern.chat_debug("boxface", "try turning off")
		lantern.turnoff(pos)
	end
	return true
end

function lantern.refuel(pos)
	-- DONE: check refueling when lamp is on, may have a bug

	local meta = minetest.get_meta(pos)
	local node = minetest.get_node(pos)

	if not node then
		return false
	end

	local oldstate = lantern.get_state(pos)

	local timer = minetest.get_node_timer(pos)
	if timer:is_started() then
		-- refueling when lamp is on
		-- Do you want to make a fire?
		-- DONE: stop timer before refueling, then turn it back on.
		lantern.turnoff(pos)
	end

	if listcontains(lantern.nodelist, node.name) then
		local burntime = meta:get_int("burntime") or 0
		if burntime < (lantern.fuel_max * lantern.fuel_time) then
			burntime = burntime + lantern.fuel_time
			meta:set_int("burntime", burntime)
			-- meta:set_string("infotext", "Lantern [" .. burntime .. "]")
			lantern.update_info(pos)

			-- set old state
			--lantern.chat_debug("boxface", "Old state was: [" .. oldstate .. "]")
			if oldstate ~= "off" then
				--lantern.chat_debug("boxface", "set old state")
				lantern.set_state(pos, oldstate)
			end
			return true
		end
	end
	-- if lamp was turned off, if not refueled turn it back on
	if oldstate ~= "off" then
		lantern.set_state(pos, oldstate)
	end
	return false
end

function lantern.take_fuel(pos, itemstack)
	-- DONE: turn off timer when taking fuel (bug)

	local meta = minetest.get_meta(pos)

	local oldstate = lantern.get_state(pos)
	if oldstate ~= "off" then
		lantern.turnoff(pos)
	end

	local burntime = meta:get_int("burntime") or 0
	if burntime >= lantern.fuel_time then
		if itemstack:item_fits(lantern.fuel) then
			itemstack:add_item(lantern.fuel)
			burntime = burntime - lantern.fuel_time
			meta:set_int("burntime", burntime)
			
			-- meta:set_string("infotext", "Lantern [" .. burntime .. "]")
			lantern.update_info(pos)
		end
	end
	if oldstate ~= "off" then
		lantern.set_state(pos, oldstate)
	end
	return itemstack
end

function lantern.get_state(pos)
	local sside = lantern.get_side(pos)
	local sname = string.format("lantern:lantern_%s_", sside)
	local node = minetest.get_node(pos)
	
	local is_off = (node.name == sname .. "off")
	local is_on = (node.name == sname .. "on")
	local is_med = (node.name == sname .. "med")
	local is_dim = (node.name == sname .. "dim")

	if is_off then
		return "off"
	elseif is_on then
		return "on"
	elseif is_med then
		return "med"
	elseif is_dim then
		return "dim"
	else
		return "error"
	end
end

function lantern.set_state(pos, state)
	if state == "off" then
		return lantern.turnoff(pos)
	elseif state == "on" then
		return lantern.turnon(pos)
	elseif state == "med" then
		return lantern.turnmed(pos)
	elseif state == "dim" then
		return lantern.turndim(pos)
	end
end

function lantern.update_info(pos)
	-- update info text
	-- DONE: get fuel in percent and display on infotext.
	local meta = minetest.get_meta(pos)
	local burntime = meta:get_int("burntime") or 0

	local sstate = lantern.get_state(pos)
	local is_off = (sstate == "off")
	local is_on = (sstate == "on")
	local is_med = (sstate == "med")
	local is_dim = (sstate == "dim")

	if is_off then
		local burn_max = lantern.fuel_max * lantern.fuel_time
		local perc = math.floor(burntime / burn_max * 100)
		local s = string.format("Lantern: %d%%", perc)
		meta:set_string("infotext", s)
		return
	elseif is_on then
		local timer = minetest.get_node_timer(pos)
		local elapsed = timer:get_elapsed()
		local burn = burntime - elapsed
		local burn_max = lantern.fuel_max * lantern.fuel_time
		local perc = math.floor(burn / burn_max * 100)
		local s = string.format("Lantern: %d%%", perc)
		meta:set_string("infotext", s)
		return
	elseif is_med then
		local timer = minetest.get_node_timer(pos)
		local elapsed = timer:get_elapsed()
		local burn = burntime * lantern.fuel_med - elapsed
		local burn_max = lantern.fuel_max * lantern.fuel_time * lantern.fuel_med
		local perc = math.floor(burn / burn_max * 100)
		local s = string.format("Lantern: %d%%", perc)
		meta:set_string("infotext", s)
		return
	elseif is_dim then
		local timer = minetest.get_node_timer(pos)
		local elapsed = timer:get_elapsed()
		local burn = burntime * lantern.fuel_dim - elapsed
		local burn_max = lantern.fuel_max * lantern.fuel_time * lantern.fuel_dim
		local perc = math.floor(burn / burn_max * 100)
		local s = string.format("Lantern: %d%%", perc)
		meta:set_string("infotext", s)
		return
	end
end

-- == CALLBACKS ==
function lantern.do_after_dig_node(pos, oldnode, oldmetadata, digger)
	-- TODO: Find a way to keep unused fuel when is less than a bottle.
	-- TODO: use lantern.turnoff() instead of calculating fuel again. (review)

	local timer = minetest.get_node_timer(pos)
	if timer:is_started() then
		-- lantern.chat_debug("boxface", "WARNING: timer was on after dig")
		timer:stop()
	end

	--lantern.chat_debug("boxface", "After Dig!")
	--lantern.chat_debug("boxface", dump(oldmetadata))
	local burntime = oldmetadata.fields["burntime"] or 0
	local fuelcount = math.floor(burntime / lantern.fuel_time)
	local inv = digger:get_inventory()

	-- take back or drop unused fuel
	if fuelcount > 0 then
		local str = string.format("%s %d", lantern.fuel, fuelcount)
		local stackfuel = ItemStack(str)
		local leftover = inv:add_item("main", stackfuel)
		if leftover:get_count() > 0 then
			minetest.item_drop(leftover, digger, pos)
		end
	end
end

function lantern.do_construct(pos)

end

function lantern.do_place(itemstack, placer, pointed_thing)
	local under = pointed_thing.under
	local above = pointed_thing.above
	local node = minetest.get_node(under)
	local wdir = minetest.dir_to_wallmounted(vector.subtract(under, above))

	-- possible bug?
	local fakestack = itemstack

	local stackname = itemstack:get_name()
	if stackname == "lantern:lantern_floor_off" then
		if wdir == 0 then
			-- ceiling
			fakestack:set_name("lantern:lantern_ceiling_off")
		elseif wdir == 1 then
			-- floor
			fakestack:set_name("lantern:lantern_floor_off")
		else
			-- wall
			fakestack:set_name("lantern:lantern_wall_off")
		end
		itemstack = minetest.item_place(fakestack, placer, pointed_thing, wdir)
		local meta = minetest.get_meta(above)
		meta:set_string("infotext", "Lantern (off)")
		itemstack:set_name("lantern:lantern_floor_off")
	end
	return itemstack
end

function lantern.do_punch(pos, node, puncher, pointed_thing)
	-- TODO: if shift key pressed switch down light (bright to dim)
	lantern.switch(pos)
	lantern.update_info(pos)
end

function lantern.do_timer(pos, elapsed)
	local sside = lantern.get_side(pos)
	local sname = string.format("lantern:lantern_%s_", sside)
	local node = minetest.get_node(pos)
	
	local is_on = (node.name == sname .. "on")
	local is_med = (node.name == sname .. "med")

	if is_on or is_med then
		-- lantern.chat_debug("boxface", "lamp runs low on fuel")

		-- patch set fuel to low level
		local meta = minetest.get_meta(pos)
		local fuel_low = math.floor(lantern.fuel_time / lantern.fuel_dim)
		meta:set_int("burntime", fuel_low)
		lantern.turnoff(pos)
		lantern.turndim(pos)
	elseif node.name == sname .. "dim" then
		-- lantern.chat_debug("boxface", "lamp runs out of fuel")

		-- patch set fuel to zero
		local meta = minetest.get_meta(pos)
		meta:set_int("burntime", 0)
		lantern.turnoff(pos)
	end
end

function lantern.do_rightclick(pos, node, clicker, itemstack, pointed_thing)
	local pname = clicker:get_player_name()
	local control = clicker:get_player_control()

	-- ==== extract fuel ====
	if control.aux1 then
		--lantern.chat_debug("boxface", "rightclick aux1")
		
		if itemstack:is_empty() or itemstack:get_name() == lantern.fuel then
			itemstack = lantern.take_fuel(pos, itemstack)
		end
		return
	end

	-- ==== refuel lamps ====
	if itemstack:get_name() == lantern.fuel then
		local isRefueled = lantern.refuel(pos)
		if isRefueled then
			itemstack:take_item(1)
			-- lantern.chat_debug(pname, "Lantern refueled")
		else
			-- lantern.chat_debug(pname, "Lantern is full")
		end
	else
		-- swith lantern on/dim/off
		-- TODO: move to on_punch
		--lantern.switch(pos)
	end
end


